package miscelania.almacenamiento;

import java.io.FileNotFoundException;
import java.io.IOException;

import java.io.RandomAccessFile;
import java.util.*;
import miscelania.memoriaIntermedia.GestorMemoriaIntermedia;

/**
 * GestorIORelacion: clase que representa de forma básica las funciones 
 * de I/O en una relación.
 *
 * @author hernan
 */
public class GestorIORelacion {

    /** El esquema de relación a ser gestionada. */
    private Relacion relacion;
    /** El gestor de almacenamiento para este gestor. */
    private GestorAlmacenamiento ga;
    /** El nombre de archivo de la relación. */
    private String nombreArchivo;

    /**
     * Constructor de un nuevo gestor I/O de relación.
     * 
     */
    public GestorIORelacion(GestorAlmacenamiento ga,
            Relacion relacion,
            String nombreArchivo) {
        this.ga = ga;
        this.relacion = relacion;
        this.nombreArchivo = nombreArchivo;
    }

    /**
     * Insertar una nueva tupla en la relación.
     * 
     * @param lista de valores.
     */
    public void insertarTupla(List<Comparable> valores) {
        try {//averiguar en que parte del archivo se encuentra la pagina donde se insertara la tupla
            // leer la última página del archivo
            int numPagina = recuperarNumeroDePaginas(getNombreArchivo());//recupera numero de paginas del archivo
            numPagina = (numPagina == 0) ? 0 : numPagina - 1;
            IdentificadorPagina idPagina =
                    new IdentificadorPagina(getNombreArchivo(), numPagina);//para poder recuperar la pagina

            Pagina pagina = ga.leerPagina(relacion, idPagina);

            int num = 0;
            if (pagina.getNumeroDeTuplas() != 0) {//tiene tuplas almacenadas
                Tupla t = pagina.recuperarTupla(pagina.getNumeroDeTuplas() - 1);
                num = t.getIdTupla().getNumero() + 1;//determina el numero de la ultima tupla existente
            }

            Tupla nuevaTupla = new Tupla(
                    new IdentificadorTupla(getNombreArchivo(), num), valores);
            
            if (!pagina.hayEspacio(nuevaTupla)) {//crea una nueva pagina
                pagina = new Pagina(relacion, new IdentificadorPagina(getNombreArchivo(),
                        numPagina + 1));
                cambiarNumeroDePaginas(getNombreArchivo(), numPagina + 2);//en la ultima posicion
            }
            pagina.adicionarTupla(nuevaTupla);

            ga.escribirPagina(pagina);//si esta en memoria intermedia hace la actualizacion sino va a disco y actualiza
        } catch (Exception e) {
            e.printStackTrace(System.err);
            System.err.println("Error de I/O mientras se insertaba la tupla "
                    + "al archivo: " + getNombreArchivo()
                    + " (" + e.getMessage() + ")");
        }
    }
    
    
    public void insertarTuplaOrdenada(List<Comparable> valores,IdentificadorTupla idTupla,Pagina pagina) {
        //IdentificadorPagina idpagina= new IdentificadorPagina(nombreArchivo,0);
        //Pagina pagina = new Pagina(relacion, idpagina);
                        //numPagina + 1));
           pagina.adicionarTuplaOrdenada(new Tupla(idTupla, valores));
           //return pagina;
    }

    /**
     * Recuperar el numero de paginas en el archivo de la relación.
     * 
     */
    public int recuperarNumeroDePaginas(String nombreArchivo)
            throws IOException, FileNotFoundException {

        RandomAccessFile archivoDB = new RandomAccessFile(getNombreArchivo(), "r");
        int paginas = (int) ((archivoDB.length() / 4096) + 0.5);//0.5 para aproximar
        archivoDB.close();
        return paginas;
    }

    public void cambiarNumeroDePaginas(String nombreArchivo, int np)
            throws IOException, FileNotFoundException {

        RandomAccessFile archivoBD = new RandomAccessFile(getNombreArchivo(), "rw");
        int paginas = (int) ((archivoBD.length() / 4096) + 0.5);
        if (paginas < np) {
            archivoBD.setLength(np * 4096);
        }
        archivoBD.close();
    }

    public String getNombreArchivo() {
        return nombreArchivo;
    }

    /**
     * Abrir un iterador de pagina sobre la relación.
     *
     */
    public Iterable<Pagina> paginas() throws IOException {
        return new PaginaIterator();
    }

    /**
     * Abrir un iterador de tuplas sobre la relación.
     *
     */
    public Iterable<Tupla> tuplas() throws IOException {
        return new TuplaIterator();
    }

    /**
     * Un iterador basico para paginas de la relación.
     */
    class PaginaIterator implements Iterable<Pagina> {//clase privada se pueden utilizar los metodos de la clase

        /** La pagina actual en la iteración. */
        private Pagina paginaActual;
        /** Numero de páginas en la relación. */
        private int numPaginas;
        /** El desplazamiento de la actual pagina. */
        private int desplazamientoPagina;

        /**
         * Construir un nuevo iterador de pagina.
         */
        public PaginaIterator() throws IOException {

            desplazamientoPagina = 0;
            numPaginas = recuperarNumeroDePaginas(getNombreArchivo());
        }

        /**
         * devolver un iterador de paginas.
         */
        @Override
        public Iterator<Pagina> iterator() {
            return new Iterator<Pagina>() {

                @Override
                public boolean hasNext() {
                    return desplazamientoPagina < numPaginas;
                }

                @Override
                public Pagina next() throws NoSuchElementException {
                    try {
                        paginaActual =
                                ga.leerPagina(relacion,
                                new IdentificadorPagina(getNombreArchivo(),
                                desplazamientoPagina++));
                        return paginaActual;
                    } catch (Exception sme) {
                        throw new NoSuchElementException("No se puede leer la "
                                + "paina por el iterador." + sme.getMessage());

                    }
                } 

                @Override
                public void remove() throws UnsupportedOperationException {
                    throw new UnsupportedOperationException("no se puede eliminar "
                            + "paginas en un iterador");
                }
            }; 
        } 
    } // Fin de clase

    /**
     * UN iterador básico para tuplas de la relación.
     */
    class TuplaIterator implements Iterable<Tupla> {

        /** La página a iterar. */
        private Iterator<Pagina> paginas;
        /** un simple iterador de tuplas. */
        private Iterator<Tupla> tuplas;
        /** Registrar la pista si hay mas tuplas que devolver. */
        private boolean masElementos;

        /**
         * Construir el iterador de tuplas.
         */
        public TuplaIterator() throws IOException {

            paginas = paginas().iterator();
            masElementos = paginas.hasNext();
            tuplas = null;
        } 

        /**
         * Verificar si hay mas tuplas que iterar
         */
        @Override
        public Iterator<Tupla> iterator() {
            return new Iterator<Tupla>() {

                public boolean hasNext() {
                    return masElementos;
                } // hasNext()

                public Tupla next() throws NoSuchElementException {
                    if (tuplas == null && masElementos) {
                        tuplas = paginas.next().iterator();
                    }

                    Tupla tupla = tuplas.next();
                    if (tuplas.hasNext()) {
                        masElementos = true;
                    } else if (paginas.hasNext()) {
                        tuplas = paginas.next().iterator();
                        masElementos = true;
                    } else {
                        masElementos = false;
                    }
                    return tupla;
                } 

                @Override
                public void remove() throws UnsupportedOperationException {
                    throw new UnsupportedOperationException("no se puede eliminar "
                            + "tuplas al "
                            + "iterar.");
                }
            }; 
        } 
    } // Fin de clase

    /**
     * test de las clases.
     */
    public static void main(String[] args) throws FileNotFoundException, IOException {
        
        Scanner lector = new Scanner(System.in);

        int op=0;
        while (op!=2){
        System.out.println("");
        System.out.println("Crear carpeta prueba en disco D");
        
            System.out.println("====================");
            System.out.println("      MENU GENERAL          ");
            System.out.println("====================");
            System.out.println("1. Ejercicio1");
            System.out.println("2. Ejercicio2");
            
            System.out.print("Ingrese una opcion: ");
            op = lector.nextInt();
            

            switch (op)
            {
                case 1:
                {
                    int tamañobuffer = 2;
                    GestorMemoriaIntermedia gmi = new GestorMemoriaIntermedia(tamañobuffer);
                    GestorAlmacenamiento ga = new GestorAlmacenamiento(null, gmi);
                int opcion = 0;
                while (opcion!=2)
                {
                    
                    System.out.println("");
                    System.out.println("====================");
                    System.out.println("      MENU          ");
                    System.out.println("====================");
                    System.out.println("1. iniciar");
                    System.out.println("2. terminar");

                    System.out.print("Ingrese una opcion: ");
                    opcion = lector.nextInt();

                    
                    //Scanner lector1 = new Scanner(System.in);
                    switch (opcion)
                    {
                        case 1:
                        {
                            try {
                            List<Atributo> atributos = new ArrayList<Atributo>();
                            atributos.add(new Atributo("integer", Integer.class));
                            atributos.add(new Atributo("string", String.class));
                            Relacion relacion = new Relacion(atributos);
                            String nombreArchivo = "d:/prueba/relacion.db";
                            ga.crearArchivo(nombreArchivo);

                            GestorIORelacion gestor =
                                    new GestorIORelacion(ga, relacion, nombreArchivo);

                            for (int i = 0; i < 108; i++) {
                                List<Comparable> v = new ArrayList<Comparable>();
                                //v.add(new Integer(i));
                                v.add(new Integer(i+50));
                                v.add(new String("12345678901234567890123456789012345678901234567890"));
                                // System.out.println("insertando: " + v);
                                gestor.insertarTupla(v);
                            }

                            System.out.println("Tuplas insertadas correctamente.");
                            System.out.println("Abriendo un cursos de tuplas...");

                            System.out.println("buffer: " + gmi);

        //                    for (Tupla tuple : gestor.tuplas()) {
        //                    System.out.println("read tupla: " + tuple);
        //                    }
        //
        //                     for (Pagina pagina : gestor.paginas()) {
        //                        System.out.println("read page: " + pagina);
        //                    }

                            } catch (Exception e) {
                                System.err.println("Exception: " + e.getMessage());
                                e.printStackTrace(System.err);
                        }

                            break;
                        }
                        case 2:{


                            for (int i =0;i<tamañobuffer;i++){
                                Pagina paginadescartada = gmi.getBuffer()[i].pagina;
                                System.out.println(paginadescartada);

                                //----------------------------
                                String nombreArchivo = paginadescartada.getIdentificadorPagina().getNombreArchivo();
                                RandomAccessFile archivoDB = new RandomAccessFile(nombreArchivo,"rw");
                                GestorIOPagina.escribirPagina(archivoDB, paginadescartada);
                                archivoDB.close();
                            //------------------------

                            }

                            break;
                        }
                    }
        }
                break;}
                case 2: {
                    int opcion = 0;
                while (opcion!=2)
                {
                    
                    System.out.println("");
                    System.out.println("====================");
                    System.out.println("      MENU          ");
                    System.out.println("====================");
                    System.out.println("1. insertar tuplas");
                    System.out.println("2. mostrar tuplas ordenadas");

                    System.out.print("Ingrese una opcion: ");
                    opcion = lector.nextInt();

                    int tamañobuffer = 2;
                    GestorMemoriaIntermedia gmi = new GestorMemoriaIntermedia(tamañobuffer);
                    GestorAlmacenamiento ga = new GestorAlmacenamiento(null, gmi);
                    
                    switch (opcion)
                    {
                        case 1:
                        {
                            try {
                            List<Atributo> atributos = new ArrayList<Atributo>();
                            atributos.add(new Atributo("integer", Integer.class));
                            atributos.add(new Atributo("string", String.class));
                            Relacion relacion = new Relacion(atributos);
                            String nombreArchivo = "d:/prueba/relacionindices.db";
                            ga.crearArchivo(nombreArchivo);

                            //crear una pagina
                            IdentificadorPagina idpagina=new IdentificadorPagina(nombreArchivo, 0);
                            Pagina pagina=new Pagina(relacion, idpagina);
                            
                            GestorIORelacion gestor =
                                    new GestorIORelacion(ga, relacion, nombreArchivo);
                            
                            //Insertar tuplas en cualquier orden
                            System.out.println("Tuplas ingresadas con la clave no ordenadas");
                                List<Comparable> v = new ArrayList<Comparable>();
                                v.add(new Integer(100));
                                v.add(new String("bla"));
                                //crear identificador de pagina
                                IdentificadorTupla idT1 = new IdentificadorTupla(nombreArchivo, 5);
                                gestor.insertarTuplaOrdenada(v,idT1,pagina);
                                System.out.println(new Tupla(idT1,v));
                                
                                List<Comparable> v2 = new ArrayList<Comparable>();
                                v2.add(new Integer(15));
                                v2.add(new String("bla bla"));
                                //crear identificador de pagina
                                IdentificadorTupla idT2 = new IdentificadorTupla(nombreArchivo, 6);
                                gestor.insertarTuplaOrdenada(v2,idT2,pagina);
                                System.out.println(new Tupla(idT2,v2));
                                
                                List<Comparable> v3 = new ArrayList<Comparable>();
                                v3.add(new Integer(5));
                                v3.add(new String("bla bla bla"));
                                //crear identificador de pagina
                                IdentificadorTupla idT3 = new IdentificadorTupla(nombreArchivo, 8);
                                gestor.insertarTuplaOrdenada(v3,idT3,pagina);
                                System.out.println(new Tupla(idT3,v3));
                                
                                List<Comparable> v4 = new ArrayList<Comparable>();
                                v4.add(new Integer(10));
                                v4.add(new String("bla bla bla bla"));
                                //crear identificador de pagina
                                IdentificadorTupla idT4 = new IdentificadorTupla(nombreArchivo, 7);
                                System.out.println(new Tupla(idT4,v4));
                                gestor.insertarTuplaOrdenada(v4,idT4,pagina);
                                
                                List<Comparable> v5 = new ArrayList<Comparable>();
                                v5.add(new Integer(10));
                                v5.add(new String("bla bla bla bla bla"));
                                //crear identificador de pagina
                                IdentificadorTupla idT5 = new IdentificadorTupla(nombreArchivo, 1);
                                System.out.println(new Tupla(idT5,v5));
                                
                                gestor.insertarTuplaOrdenada(v5,idT5,pagina);
                                
                                List<Comparable> v6 = new ArrayList<Comparable>();
                                v6.add(new Integer(10));
                                v6.add(new String("bla bla bla bla bla"));
                                //crear identificador de pagina
                                IdentificadorTupla idT6 = new IdentificadorTupla(nombreArchivo, 3);
                                System.out.println(new Tupla(idT6,v6));
                                
                                gestor.insertarTuplaOrdenada(v6,idT6,pagina);
                                
                            
                                //String nombreArchivo = paginadescartada.getIdentificadorPagina().getNombreArchivo();
                                RandomAccessFile archivoDB = new RandomAccessFile(nombreArchivo,"rw");
                                GestorIOPagina.escribirPagina(archivoDB,pagina);
                                archivoDB.close();
                           

//                            System.out.println("Tuplas insertadas correctamente.");
//                            System.out.println("Abriendo un cursos de tuplas...");

                            //System.out.println("buffer: " + gmi);


                            } catch (Exception e) {
                                System.err.println("Exception: " + e.getMessage());
                                e.printStackTrace(System.err);
                        }

                            break;
                        }
                        case 2:{

                            System.out.println("Tuplas ordenadas por la clave leidas de disco");
                            List<Atributo> atributos = new ArrayList<Atributo>();
                            atributos.add(new Atributo("integer", Integer.class));
                            atributos.add(new Atributo("string", String.class));
                            Relacion relacion = new Relacion(atributos);
                            String nombreArchivo = "d:/prueba/relacionindices.db";
                            IdentificadorPagina idPagina =
                            new IdentificadorPagina(nombreArchivo, 0);
                            System.out.println(ga.leerPagina(relacion,idPagina));
                            break;
                            }

                            
                        
                    }
                }
                break;
                    //ejercicio 2
                    
                }
                     default:
                        break;
            }
        }}
} 

